package net.lastcoder.ifserver.terminate;

import net.lastcoder.ifserver.Server;
import net.lastcoder.ifserver.Constants;
import net.lastcoder.ifserver.Globals;
import net.lastcoder.ifserver.command.CommandManager;
import net.lastcoder.ifserver.nio.ConnectPrivacy;
import net.lastcoder.ifserver.nio.Connection;
import net.lastcoder.ifserver.nio.ConnectionCloseListener;
import net.lastcoder.ifserver.nio.NIOConnection;
import net.lastcoder.ifserver.session.Session;
import net.lastcoder.ifserver.session.UlaID;

import org.apache.log4j.Appender;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.StringTokenizer;

/**
 * auth: WenYF
 * date: 2016/11/23
 */
class TerminateHandler extends IoHandlerAdapter {
    private static final Logger Log = LoggerFactory.getLogger(TerminateHandler.class);

    protected static final String CONNECTION = "CONNECTION";
    protected static final String APPENDER = "APPENDER";

    private CommandManager commandManager;
    private TerminateManager terminateManager;
    private TerminateLogFilter myLogFilter;
    private ConnectionCloseListener connectionCloseListener = new ConnectionCloseListener() {
        @Override
        public void onConnectionClose(Object handback) {
            TerminateSession session = (TerminateSession) handback;
            terminateManager.removeTerminateSession(session);
            TerminateConsoleAppender appender = (TerminateConsoleAppender) session.getAttribute(APPENDER);
            unregisteLog(appender);
            appender.closeWriter();
        }
    };

    TerminateHandler() {
        commandManager = CommandManager.getInstance();
        terminateManager = TerminateManager.getInstance();
        myLogFilter = new TerminateLogFilter();
        myLogFilter.activateOptions();
    }

    @Override
    public void sessionOpened(IoSession session) throws Exception {
        boolean ban = false;
        final SocketAddress remoteAddress = session.getRemoteAddress();
        if (remoteAddress == null) {
            ban = true;
        } else {
            final InetSocketAddress socketAddress = (InetSocketAddress) remoteAddress;
            final InetAddress inetAddress = socketAddress.getAddress();
            if (ConnectPrivacy.getInstance().isBan(inetAddress.getHostAddress())) {
                ban = true;
            }
        }

        if (ban) {
            session.close(true);
            return;
        }

        final NIOConnection connection = createNIOConnection(session);
        session.setAttribute(CONNECTION, connection);
        session.getConfig().setReaderIdleTime(getMaxIdleTime());
    }

    @Override
    public void sessionClosed(IoSession session) throws Exception {
        super.sessionClosed(session);

        final Connection connection = (Connection) session.getAttribute(CONNECTION);
        if (connection != null) {
            connection.close();
        }
    }

    @Override
    public void sessionIdle(IoSession session, IdleStatus status) throws Exception {
        if (session.getIdleCount(status) >= 1) {
            // Get the connection for this session
            final Connection connection = (Connection) session.getAttribute(CONNECTION);
            if (connection != null) {
                connection.deliverRawData("timeout".getBytes());
                // Close idle connection
                if (Log.isDebugEnabled()) {
                    Log.debug("ConnectionHandler: Closing connection that has been idle: " + connection);
                }
                connection.close();
            }
        }
    }

    @Override
    public void exceptionCaught(IoSession session, Throwable cause) throws Exception {
        Log.warn("Closing connection due to exception in session: " + session, cause);

        try {
            final Connection connection = (Connection) session.getAttribute(CONNECTION);
            byte[] error = new byte[1];
            error[0] = '\0';
            connection.deliverRawData(error);
        } finally {
            final Connection connection = (Connection) session.getAttribute(CONNECTION);
            if (connection != null) {
                connection.close();
            }
        }
    }

    @Override
    public void messageReceived(IoSession session, Object message) throws Exception {
        String str = (String) message;
        Connection connection = (Connection) session.getAttribute(CONNECTION);
        if (str.equalsIgnoreCase("ping")) {
            TerminateSession terminateSession = (TerminateSession) connection.getOwner();
            terminateSession.deliverRawData("pong".getBytes());
        } else if (str.startsWith("login") || str.startsWith("Login")) {
            boolean ret = false;
            ret = terminateManager.login(str);
            if (ret) {
                // 登陆成功，设置log监听
                TerminateSession terminateSession = TerminateSession.createSession(connection
                        , UlaID.fromAddress(terminateManager.generateName() + "/0"));
                connection.init(terminateSession);
                terminateSession.setStatus(Session.STATUS_AUTHENTICATED);
                terminateManager.addTerminateSession(terminateSession);
                TerminateConsoleAppender appender = new TerminateConsoleAppender(terminateSession);
                terminateSession.setAttribute(APPENDER, appender);
                appender.addFilter(myLogFilter);
                appender.activateOptions();
                registeLog(appender);

                connection.registerCloseListener(connectionCloseListener, 0);

                connection.deliverRawData(("welcome to use " + Server.getInstance().getServerName()
                        + " terminate!\n").getBytes());
                connection.deliverRawData(("you terminate name is " + terminateSession.getUsername() + "\n")
                        .getBytes());
            } else {
                connection.deliverRawData("deny".getBytes());
                connection.close();
            }
        } else if ("playerExit".equalsIgnoreCase(str) || "halt".equalsIgnoreCase(str)) {
            connection.deliverRawData("bye!".getBytes());
            connection.close();
        } else {
            TerminateSession terminateSession = (TerminateSession) connection.getOwner();
            if (terminateSession != null) {
                if (terminateSession.getStatus() == Session.STATUS_AUTHENTICATED) {
                    terminateSession.incrementClientPacketCount();
                    String ret = commandManager.processCommand(str);
                    terminateSession.deliverRawData(ret.getBytes(Constants.DEFAUT_CAHRSET));
                } else {
                    terminateSession.deliverRawData("not login".getBytes());
                    terminateSession.close();
                }
            } else {
                connection.deliverRawData("not login".getBytes());
                connection.close();
            }
        }
    }

    @Override
    public void messageSent(IoSession session, Object message) throws Exception {
        super.messageSent(session, message);
    }

    private NIOConnection createNIOConnection(IoSession session) {
        return new NIOConnection(session);
    }

    private int getMaxIdleTime() {
        return Globals.getXMLProperty("terminate.session.idle.time", Constants.DAY * 10) / 1000;
    }

    private void registeLog(Appender appender) {
        org.apache.log4j.LogManager.getRootLogger().addAppender(appender);
    }

    private void unregisteLog(Appender appender) {
        org.apache.log4j.LogManager.getRootLogger().removeAppender(appender);
    }
}
